package com.coderknock.spring.boot.aspect;

import com.alibaba.fastjson.JSON;
import com.coderknock.spring.boot.jedis.executor.JedisExecutor;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;

/**
 * <p></p>
 *
 * @author 三产
 * @version 1.0
 * @date 2017-08-27
 * @QQGroup 213732117
 * @website http://www.coderknock.com
 * @copyright Copyright 2017 拿客 coderknock.com  All rights reserved.
 * @since JDK 1.8
 */
@Aspect
@Component
public class RedisCacheAspect {
    private static final Logger logger = LogManager.getLogger(RedisCacheAspect.class);

    /**
     * 方法调用前，先查询缓存。如果存在缓存，则返回缓存数据，阻止方法调用;
     * 如果没有缓存，则调用业务方法，然后将结果放到缓存中
     * 缓存
     * @param jp
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.coderknock.spring.boot.mybatis.mapper.ArticleMapper.select*(..))")
    public Object cache(ProceedingJoinPoint jp) throws Throwable {
        // 得到类名、方法名和参数
        String clazzName = jp.getTarget().getClass().getName();
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();

        // 根据类名，方法名和参数生成key
        String key = genKey(clazzName, methodName, args);

        // 得到被代理的方法
        Method me = ((MethodSignature) jp.getSignature()).getMethod();
        // 得到被代理的方法上的注解
        RedisCache annotation = me.getAnnotation(RedisCache.class);
        //如果没有加 RedisCache 注解说明不缓存
        if (annotation == null) {
            // 调用数据库查询方法
            return jp.proceed(args);
        }
        Class modelType = annotation.type();
        //采用 Hash 结构 key 我们存储的类型 Artice 文章表  包名、类名
        // 属性 key 我们方法的一个参数 value 实际返回的结果 序列化 json
        boolean exists = JedisExecutor.execute(jedis -> jedis.hexists(modelType.getName(), key));

        // result是方法的最终返回结果
        Object result = null;
        if (!exists) {
            // 缓存未命中
            logger.info("缓存未命中");

            // 调用数据库查询方法
            result = jp.proceed(args);

            // 序列化查询结果
            String json = serialize(result);

            // 序列化结果放入缓存
            JedisExecutor.executeNR(jedis -> jedis.hset(modelType.getName(), key, json));
        } else {
            // 缓存命中
            logger.info("缓存命中, key={" + modelType.getName() + "   " + key + "}");
            String value = JedisExecutor.execute(jedis -> {
                return jedis.hget(modelType.getName(), key);
            });
            // 得到被代理方法的返回值类型
            Class returnType = ((MethodSignature) jp.getSignature()).getReturnType();

            // 反序列化从缓存中拿到的json
            result = deserialize(value, returnType, modelType);
            logger.debug("反序列化结果 = {" + JSON.toJSONString(result) + "}");
        }

        return result;
    }

    /**
     * 在方法调用前清除缓存，然后调用业务方法
     *
     * @param jp
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.coderknock.spring.boot.mybatis.mapper.ArticleMapper.insert*(..))" +
            "|| execution(* com.coderknock.spring.boot.mybatis.mapper.ArticleMapper.update*(..))" +
            "|| execution(* com.coderknock.spring.boot.mybatis.mapper.ArticleMapper.delete*(..))" +
            "|| execution(* com.coderknock.spring.boot.mybatis.mapper.ArticleMapper.save*(..))")
    public Object evictCache(ProceedingJoinPoint jp) throws Throwable {

        // 得到被代理的方法
        Method me = ((MethodSignature) jp.getSignature()).getMethod();
        RedisEvict annotation = me.getAnnotation(RedisEvict.class);
        //如果没有加 RedisCache 注解说明不缓存
        if (annotation != null) {
            // 得到被代理的方法上的注解
            Class modelType = me.getAnnotation(RedisEvict.class).type();
            logger.info("清空缓存:{" + modelType.getName() + "}");

            // 清除对应缓存
            JedisExecutor.executeNR(jedis -> jedis.del(modelType.getName()));
        }
        return jp.proceed(jp.getArgs());
    }


    /**
     * 根据类名、方法名和参数生成key
     *
     * @param clazzName
     * @param methodName
     * @param args       方法参数
     * @return
     */
    protected String genKey(String clazzName, String methodName, Object[] args) {
        StringBuilder sb = new StringBuilder(clazzName);
        sb.append(":");
        sb.append(methodName);
        sb.append(":");

        for (Object obj : args) {
            sb.append(obj.toString());
            sb.append(":");
        }

        return sb.toString();
    }

    protected String serialize(Object target) {
        return JSON.toJSONString(target);
    }

    protected Object deserialize(String jsonString, Class clazz, Class modelType) {
        // 序列化结果应该是List对象
        if (clazz.isAssignableFrom(List.class)) {
            return JSON.parseArray(jsonString, modelType);
        }

        // 序列化结果是普通对象
        return JSON.parseObject(jsonString, clazz);
    }
}
